﻿#region Copyright
/// <copyright>
/// Copyright (c) 2008 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace Osh.Media
{
	public class MediaTreeNavigator
	{
		private IMediaTree _tree;
		private IMediaFolder _currentFolder;
		private IMedia _currentMedia;

		private MediaTreeNavigator()
		{
		}

		public MediaTreeNavigator(IMediaTree tree)
		{
			if(tree == null)
				throw new ArgumentNullException("tree");
			_tree = tree;
			MoveToFirstMedia();
		}

		public bool MoveToNextFolder()
		{
			if(_currentFolder == null)
				return MoveToFirstMedia();

			IList<IMediaFolder> folders = _tree.Folders;
			int currentFolderIndex = folders.IndexOf(_currentFolder);
			if(currentFolderIndex == -1)
				return MoveToFirstMedia();

			for(int i = currentFolderIndex + 1; i < folders.Count; i++)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					_currentFolder = folder;
					_currentMedia = folder.Items[0];
					return true;
				}
			}
			return false;
		}

		public bool MoveToPreviousFolder()
		{
			if(_currentFolder == null)
				return MoveToFirstMedia();

			IList<IMediaFolder> folders = _tree.Folders;
			int currentFolderIndex = folders.IndexOf(_currentFolder);
			if(currentFolderIndex == -1)
				return MoveToFirstMedia();

			for(int i = currentFolderIndex - 1; i >= 0; i--)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					_currentFolder = folder;
					_currentMedia = folder.Items[0];
					return true;
				}
			}
			return false;
		}

		public bool MoveToFirstMedia()
		{
			IList<IMediaFolder> folders = _tree.Folders;
			for(int i = 0; i < folders.Count; i++)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					_currentFolder = folder;
					_currentMedia = folder.Items[0];
					return true;
				}
			}
			_currentFolder = null;
			_currentMedia = null;
			return false;
		}

		public bool MoveToLastMedia()
		{
			IList<IMediaFolder> folders = _tree.Folders;
			for(int i = folders.Count - 1; i >= 0; i--)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					_currentFolder = folder;
					_currentMedia = folder.Items[folder.Items.Count - 1];
					return true;
				}
			}
			_currentFolder = null;
			_currentMedia = null;
			return false;
		}

		[ComVisible(false)]
		public bool MoveToMedia(Uri mediaUri)
		{
			MediaTreeNavigator navigator = this.Clone();
			do
			{
				if(navigator.CurrentMedia == null)
					continue;
				if(navigator.CurrentMedia.Uri == mediaUri)
				{
					if(this.MoveTo(navigator))
						return true;
				}
			}
			while(navigator.MoveToNextMedia());
			return false;
		}

		public bool MoveToMedia(int offset)
		{
			if(offset >= 0)
				return MoveToNextMedia(offset);
			else
				return MoveToPreviousMedia(-offset);
		}

		public bool MoveToNextMedia()
		{
			return MoveToNextMedia(1);
		}

		private bool MoveToNextMedia(int offset)
		{
			if(offset < 0)
				return MoveToPreviousMedia(-offset);

			if(_currentFolder == null)
				return MoveToFirstMedia();

			IList<IMediaFolder> folders = _tree.Folders;
			int currentFolderIndex = folders.IndexOf(_currentFolder);
			if(currentFolderIndex == -1)
				return MoveToFirstMedia();

			int currentMediaIndex = _currentFolder.Items.IndexOf(_currentMedia);
			if(currentMediaIndex == -1)
				return MoveToFirstMedia();

			for(int i = currentFolderIndex; i < folders.Count; i++)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					int startIndex = (i == currentFolderIndex) ? currentMediaIndex : 0;
					int maxOffset = folder.Items.Count - startIndex - 1;
					if(offset <= maxOffset)
					{
						_currentFolder = folder;
						_currentMedia = folder.Items[startIndex + offset];
						return true;
					}
					else
						offset -= maxOffset + 1;
				}
			}
			
			if(!MoveToLastMedia())
				return false;

			return currentFolderIndex != folders.IndexOf(_currentFolder) ||
				currentMediaIndex != _currentFolder.Items.IndexOf(_currentMedia);
		}

		public bool MoveToPreviousMedia()
		{
			return MoveToPreviousMedia(1);
		}

		private bool MoveToPreviousMedia(int offset)
		{
			if(offset < 0)
				return MoveToNextMedia(-offset);

			if(_currentFolder == null)
				return MoveToFirstMedia();

			IList<IMediaFolder> folders = _tree.Folders;
			int currentFolderIndex = folders.IndexOf(_currentFolder);
			if(currentFolderIndex == -1)
				return MoveToFirstMedia();

			int currentMediaIndex = _currentFolder.Items.IndexOf(_currentMedia);
			if(currentMediaIndex == -1)
				return MoveToFirstMedia();

			for(int i = currentFolderIndex; i >= 0; i--)
			{
				IMediaFolder folder = folders[i];
				if(folder.Items.Count > 0)
				{
					int startIndex = (i == currentFolderIndex) ? currentMediaIndex : (folder.Items.Count - 1);
					int maxOffset = startIndex;
					if(offset <= maxOffset)
					{
						_currentFolder = folder;
						_currentMedia = folder.Items[startIndex - offset];
						return true;
					}
					else
						offset -= maxOffset + 1;
				}
			}

			if(!MoveToFirstMedia())
				return false;

			return currentFolderIndex != folders.IndexOf(_currentFolder) ||
				currentMediaIndex != _currentFolder.Items.IndexOf(_currentMedia);
		}

		public bool MoveTo(MediaTreeNavigator position)
		{
			if(position == null)
				throw new ArgumentNullException("position");

			if(position._tree != this._tree)
				return false;

			this._currentFolder = position._currentFolder;
			this._currentMedia = position._currentMedia;
			return true;
		}

		public int CalcDistance(MediaTreeNavigator toPosition)
		{
			if(this._tree != toPosition._tree)
				throw new ArgumentOutOfRangeException("Cannot calculate distance between different media trees.");
			if(this._currentMedia == null)
				throw new InvalidOperationException("Source position is invalid.");
			if(toPosition._currentMedia == null)
				throw new InvalidOperationException("Destination position in invalid.");

			MediaTreeNavigator currentPosition = this.Clone();

			IList<IMediaFolder> folders = _tree.Folders;
			int currentFolderIndex = folders.IndexOf(currentPosition._currentFolder);
			int toFolderIndex = folders.IndexOf(toPosition._currentFolder);

			if(currentFolderIndex == -1)
				throw new InvalidOperationException("Source position is invalid.");
			if(toFolderIndex == -1)
				throw new InvalidOperationException("Destination position in invalid.");

			IMediaFolder currentFolder = folders[currentFolderIndex];
			IMediaFolder toFolder = folders[toFolderIndex];
			int currentMediaIndex = currentFolder.Items.IndexOf(currentPosition._currentMedia);
			int toMediaIndex = toFolder.Items.IndexOf(toPosition._currentMedia);
			if(currentMediaIndex == -1)
				throw new InvalidOperationException("Source position is invalid.");
			if(toMediaIndex == -1)
				throw new InvalidOperationException("Destination position in invalid.");

			int distance = 0;
			if(currentFolderIndex == toFolderIndex)
			{
				distance = toMediaIndex - currentMediaIndex;
			}
			else
			{
				int sign = (currentFolderIndex <= toFolderIndex ? 1 : -1);
				if(currentFolderIndex > toFolderIndex)
				{
					int temp = currentFolderIndex;
					currentFolderIndex = toFolderIndex;
					toFolderIndex = temp;
				}

				distance += currentFolder.Items.Count - currentMediaIndex;
	
				for(int i = currentFolderIndex + 1; i < toFolderIndex; i++)
					distance += folders[i].Items.Count;

				distance += toMediaIndex;
				distance *= sign;
			}

			return distance;
		}

		public MediaTreeNavigator Clone()
		{
			MediaTreeNavigator clone = new MediaTreeNavigator();
			clone._tree = this._tree;
			clone._currentFolder = this._currentFolder;
			clone._currentMedia = this._currentMedia;
			return clone;
		}

		public IMediaTree Tree
		{
			get { return _tree; }
		}

		public IMediaFolder CurrentFolder
		{
			get { return _currentFolder; }
		}

		public IMedia CurrentMedia
		{
			get { return _currentMedia; }
		}

	}
}
